package com.globefrog.framework.web.interceptor;

import com.globefrog.framework.common.annontation.TimeTracker;
import com.globefrog.framework.web.RequestContextService;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.context.event.EventListener;
import org.springframework.http.client.support.HttpRequestWrapper;
import org.springframework.util.Assert;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

/**
 * 耗时追踪拦截器.
 *
 * @author : caijinbang
 * @date : Create in 2021-01-17
 */
@Slf4j
public class TimeTrackerInterceptor extends HandlerInterceptorAdapter {

  private static final LoadingCache<String, Boolean> CACHE;
  private RequestContextService requestContextService;
  private RequestContextInterceptor requestContextInterceptor;
  private static final String PO = "-";


  static {
    CACHE = CacheBuilder.newBuilder().maximumSize(20L).expireAfterWrite(5L, TimeUnit.SECONDS)
        .build(new CacheLoader<String, Boolean>() {
          public Boolean load(String key) throws Exception {
            return Boolean.FALSE;
          }
        });
  }

  @Autowired
  private LoggingSystem loggingSystem;

  public TimeTrackerInterceptor() {
  }

  /**
   * 初始化强制日志输出等级.
   */
  @EventListener({ApplicationReadyEvent.class})
  public void initForeLogLevel() {
    this.loggingSystem.setLogLevel(
        TimeTrackerInterceptor.ForceLog.class
            .getName(), LogLevel.WARN);
  }

  /**
   * 拦截完成之后执行
   *
   * @param request  请求
   * @param response 响应
   * @param handler  处理器
   * @param ex       异常
   * @throws Exception 异常
   */
  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
      Object handler, Exception ex) throws Exception {
    if (handler instanceof HandlerMethod && request instanceof HttpRequestWrapper) {
      if (ForceLog.log.isTraceEnabled()) {
        ForceLog.log.warn("force write all trace log!");
      }

      HandlerMethod handlerMethod = (HandlerMethod) handler;
      // controller类
      String controllerName = handlerMethod.getBeanType().getName();
      // 类方法
      String classMethod = controllerName + PO + ((HandlerMethod) handler).getMethod().getName();
      // 支持controller和类方法的强制输出
      if (CACHE.get(controllerName) || CACHE.get(classMethod)) {
        ForceLog.log.warn("force write trace log!!!");
      }
      //
      Assert.isTrue(requestContextInterceptor.initialized(request),
          "trackInterceptor depends on requestContextInterceptor, please check WebConfig");
      TimeTracker timeTracker = findMethodOrClassLevelAnnotation(handler);
      if (timeTracker != null) {
        this.trackProcess(timeTracker);
      }
    }

  }

  private void trackProcess(TimeTracker track) {
    Date startTime = this.requestContextService.getRequestDate();
    long elapsedTime = System.currentTimeMillis() - startTime.getTime();
    if (warningEnabled(track) && elapsedTime > track.waringThreshold()) {
      log.warn("process took longer than track threshold, elapsedTime={}(ms)", elapsedTime);
    }

  }

  public static synchronized void forceTrace(String controllerName, String methodName) {
    if (StringUtils.isEmpty(methodName)) {
      CACHE.put(controllerName.trim(), true);
    } else {
      CACHE.put(controllerName.trim() + "-" + methodName.trim(), true);
    }

  }

  /**
   * 是否警告.
   *
   * @param track 注解对象
   * @return 是否警告
   */
  private boolean warningEnabled(TimeTracker track) {
    return track.waringThreshold() > 0;
  }

  @Autowired
  public void setRequestContextService(RequestContextService setRequestContextService) {
    this.requestContextService = setRequestContextService;
  }

  @Autowired
  public void setRequestContextInterceptor(
      RequestContextInterceptor requestContextInterceptor) {
    this.requestContextInterceptor = requestContextInterceptor;
  }


  private static TimeTracker findMethodOrClassLevelAnnotation(Object handler) {
    if (!(handler instanceof HandlerMethod)) {
      return null;
    }

    TimeTracker timeTracker = ((HandlerMethod) handler).getMethodAnnotation(TimeTracker.class);
    if (timeTracker != null) {
      return timeTracker;
    } else {
      Class targetClass = ((HandlerMethod) handler).getBeanType();

      TimeTracker classTimeTracker = (TimeTracker) targetClass.getAnnotation(TimeTracker.class);
      if (classTimeTracker != null) {
        return classTimeTracker;
      }

      targetClass = targetClass.getSuperclass();
      while (!Object.class.equals(targetClass)) {
        classTimeTracker = (TimeTracker) targetClass.getAnnotation(TimeTracker.class);
        if (classTimeTracker != null) {
          return classTimeTracker;
        }
        targetClass = targetClass.getSuperclass();
      }
      return null;
    }

  }

  @Slf4j
  public static class ForceLog {

    public ForceLog() {
    }
  }
}
